﻿@page "/"
@using System.Reflection
@inject NavigationManager NavManager

<MudThemeProvider Theme="@_customTheme" IsDarkMode="@_isDarkMode" />

<MudLayout>
    <MudRTLProvider RightToLeft="_rightToLeft">
        <MudAppBar Elevation="0">
            <MudIconButton Icon="@Icons.Material.Filled.Menu" Edge="Edge.Start" OnClick="DocsDrawerToggle" aria-label="Toggle drawer" />
            <MudSpacer />
            <MudAutocomplete @ref="_autocomplete" T="Type" Placeholder="Search all tests" SearchFunc="Search" Variant="Variant.Outlined" ValueChanged="OnSearchResult" Class="docs-search-bar" AdornmentIcon="@Icons.Material.Filled.Search" AdornmentAriaLabel="Search">
                <ItemTemplate Context="result">
                    <MudText>@result.Name</MudText> <MudText Typo="Typo.body2">@GetDescription(result)</MudText>
                </ItemTemplate>
            </MudAutocomplete>
            <MudSpacer />
            <MudTooltip Text="@ThemeLabel">
                <MudIconButton Icon="@ThemeIcon" OnClick="ToggleTheme" aria-label="@ThemeLabel" />
            </MudTooltip>
            <MudTooltip Text="@(_rightToLeft ? "Left-to-right" : "Right-to-left")">
                <MudIconButton Icon="@(_rightToLeft ? @Icons.Material.Filled.FormatTextdirectionLToR : @Icons.Material.Filled.FormatTextdirectionRToL)" OnClick="() => _rightToLeft = !_rightToLeft" aria-label="@(_rightToLeft ? "Left-to-right" : "Right-to-left")" />
            </MudTooltip>
        </MudAppBar>

        <MudDrawer Open="@_drawerOpen" Overlay="false" Variant="DrawerVariant.Responsive" ClipMode="DrawerClipMode.Always" Breakpoint="Breakpoint.Sm" Style="overflow-y: scroll">
            <MudDrawerHeader Class="flex-column gap-2" Style="min-height: auto !important">
                <div class="flex-column align-start justify-center">
                    <MudText Typo="Typo.h6">Test Components</MudText>

                    <MudText Typo="Typo.subtitle2" title="@_selectedType?.Name" style="overflow: hidden; text-overflow: ellipsis;">
                        @if (_selectedType == null)
                        {
                            @("None selected")
                        }
                        else
                        {
                            @_selectedType?.Name
                        }
                    </MudText>
                </div>

                <div class="d-flex flex-row align-center gap-2">
                    <MudTextField T="string" @bind-Value="@_currentSearchText" Immediate="true" FullWidth="true"
                                  Typo="Typo.subtitle1" Placeholder="Search by component" Clearable DebounceInterval="200" />

                    <MudTooltip Text="@(_expandedState ? "Collapse all" : "Expand all")">
                        <MudIconButton Icon="@(_expandedState ? CollapseAllIcon : ExpandAllIcon)" Size="Size.Small"
                                       OnClick="@(() => ToggleExpanded())" />
                    </MudTooltip>
                </div>
            </MudDrawerHeader>

            <MudNavMenu>
                @for (int index = 0; index < _availableDirectories.Length; index++)
                {
                    var dir = _availableDirectories[index];
                    var typesInDir = _availableComponentTypes.Where(x => _typeDirectories?[x] == dir);

                    if (dir is not null && (!ShouldFilter || dir.Contains(_currentSearchText, StringComparison.OrdinalIgnoreCase)))
                    {
                        <MudNavGroup Expanded="@_mudGroupExpanded[index]" Title="@dir">
                            @foreach (var type in typesInDir)
                            {
                                <MudNavLink Class="@(_selectedType == type ? "mode-links active" : "")"
                                            OnClick="@(() => _selectedType = type)" @key="type.Name">@type.Name</MudNavLink>
                            }
                        </MudNavGroup>
                    }
                }
            </MudNavMenu>
        </MudDrawer>

        <MudMainContent Class="mt-4">
            <div class="d-flex flex-row align-center justify-center">
                <MudIcon Icon="@Icons.Material.Filled.Description" />
                <MudText>
                    @if (_selectedType == null)
                    {
                        @("No test selected")
                    }
                    else
                    {
                        @GetDescription(_selectedType)
                    }
                </MudText>
            </div>

            <MudPaper Class="pa-4 ma-4" Elevation="5">
                @if (_selectedType == null)
                {
                    <MudText>Select a test using the search or nav menu</MudText>
                }
                else
                {
                    @* Prevent double popovers! *@
                    <CascadingValue Name="UsePopoverProvider" Value="false" IsFixed="true">
                        @TestComponent()
                    </CascadingValue>
                }
            </MudPaper>
        </MudMainContent>
    </MudRTLProvider>
</MudLayout>
<style>
    .docs-search-bar .mud-input {
        height: 42px;
    }

    .docs-search-bar.mud-input-control {
        background-color: rgba(255,255,255,.15);
        margin-bottom: 5px;
        height: 42px;
        border-radius: var(--mud-default-borderradius);
    }

    .docs-search-bar.mud-input-control .mud-input-root, .docs-search-bar.mud-input-control .mud-icon-default {
        color: #fafafa;
    }

    .docs-search-bar .mud-input.mud-input-outlined .mud-input-outlined-border {
        border: none;
        border-radius: var(--mud-default-borderradius);
    }

    .mode-links.active {
        color: var(--mud-palette-primary);
        background-color: var(--mud-palette-primary-hover);
    }

    .mud-nav-link {
        padding: 4px;
    }

    .mud-collapse-container {
        transition-duration: 100ms;
    }

    .mud-appbar .mud-icon-button {
        color: var(--mud-palette-appbar-text) !important;
    }
</style>

@code {

    private const string ExpandAllIcon = """<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>expand-all-outline</title><path d="M4,2A2,2 0 0,0 2,4V14H4V4H14V2H4M8,6A2,2 0 0,0 6,8V18H8V8H18V6H8M20,12V20H12V12H20M20,10H12A2,2 0 0,0 10,12V20A2,2 0 0,0 12,22H20A2,2 0 0,0 22,20V12A2,2 0 0,0 20,10M19,17H17V19H15V17H13V15H15V13H17V15H19V17Z" /></svg>""";
    private const string CollapseAllIcon = """<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>collapse-all-outline</title><path d="M4,2A2,2 0 0,0 2,4V14H4V4H14V2H4M8,6A2,2 0 0,0 6,8V18H8V8H18V6H8M20,12V20H12V12H20M20,10H12A2,2 0 0,0 10,12V20A2,2 0 0,0 12,22H20A2,2 0 0,0 22,20V12A2,2 0 0,0 20,10M19,17H13V15H19V17Z" /></svg>""";

    private bool _rightToLeft;
    private Type? _selectedType;
    private bool _drawerOpen = true;
    private bool _expandedState = true;
    private bool[] _mudGroupExpanded = [];
    private Type[] _availableComponentTypes = [];
    private string?[] _availableDirectories = [];
    private string _currentSearchText = string.Empty;
    private Dictionary<Type, string>? _typeDirectories;
    private MudAutocomplete<Type> _autocomplete = null!;
    private bool _isDarkMode;
    private readonly MudTheme _customTheme = new()
    {
        LayoutProperties = new LayoutProperties
        {
            DrawerWidthLeft = "400px"
        }
    };

    private bool ShouldFilter => !string.IsNullOrWhiteSpace(_currentSearchText) && _currentSearchText.Length > 2;

    private void ToggleTheme() => _isDarkMode = !_isDarkMode;

    private string ThemeIcon => _isDarkMode ? Icons.Material.Filled.LightMode : Icons.Material.Filled.DarkMode;

    private string ThemeLabel => _isDarkMode ? "Light mode" : "Dark mode";

    private void DocsDrawerToggle() => _drawerOpen = !_drawerOpen;

    private void ToggleExpanded()
    {
        _expandedState = !_expandedState;
        for (int i = 0; i < _mudGroupExpanded.Length; i++)
        {
            _mudGroupExpanded[i] = _expandedState;
        }
    }

    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();

        _availableComponentTypes = GetTestComponentTypes().ToArray();
        _availableDirectories = GetTestFolders().ToArray();

        _mudGroupExpanded = new bool[_availableDirectories.Length];
        ToggleExpanded();

        _typeDirectories = _availableComponentTypes.ToDictionary(
            type => type,
            type => type.Namespace?.Split('.').LastOrDefault() ?? string.Empty
        );

        ParseQueryString();
        NavManager.LocationChanged += HandleLocationChanged;
    }

    private void HandleLocationChanged(object? sender, LocationChangedEventArgs e)
    {
        ParseQueryString();
        StateHasChanged();
    }

    public void Dispose()
    {
        NavManager.LocationChanged -= HandleLocationChanged;
    }

    private void ParseQueryString()
    {
        var uri = new Uri(NavManager.Uri);
        var query = uri.Query;

        if (string.IsNullOrEmpty(query))
            return;

        if (query.StartsWith('?'))
            query = query.Substring(1);

        var queryParams = query.Split('&');

        foreach (var param in queryParams)
        {
            var parts = param.Split('=');
            if (parts.Length == 2)
            {
                var key = parts[0];
                var value = Uri.UnescapeDataString(parts[1]);

                if (string.Equals(key, "component", StringComparison.OrdinalIgnoreCase))
                {
                    var componentType = _availableComponentTypes.FirstOrDefault(t =>
                        t.Name.Equals(value, StringComparison.OrdinalIgnoreCase));

                    if (componentType != null)
                    {
                        _selectedType = componentType;
                        StateHasChanged();
                        break;
                    }
                }
            }
        }
    }

    private void UpdateQueryString(Type componentType)
    {
        if (componentType == null) return;

        var uri = new Uri(NavManager.Uri);
        var baseUrl = uri.GetLeftPart(UriPartial.Path);

        // Create the new query string
        var newUrl = $"{baseUrl}?component={componentType.Name}";

        // Update URL without navigation (false parameter)
        NavManager.NavigateTo(newUrl, false);
    }

    private RenderFragment TestComponent() => builder =>
    {
        if (_selectedType is null)
        {
            return;
        }

        builder.OpenComponent(0, _selectedType);
        builder.CloseComponent();
    };

    private static IEnumerable<Type> GetTestComponentTypes()
    {
        var types = typeof(Program).Assembly.GetTypes()
            .Where(type => type.Name.Contains("Test"))
            .Where(type => !type.Name.StartsWith("<"))
            .Where(type => type.GetInterfaces().Contains(typeof(IComponent)))
            .OrderBy(type => type.Name);

        foreach (var type in types)
        {
            yield return type;
        }
    }

    private IEnumerable<string?> GetTestFolders()
    {
        return _availableComponentTypes
            .Select(type => type.Namespace?.Split('.').LastOrDefault())
            .Where(namespaceName => !string.IsNullOrEmpty(namespaceName))
            .Distinct()
            .OrderBy(namespaceName => namespaceName);
    }

    private Task<IEnumerable<Type>> Search(string text, CancellationToken token)
    {
        if (string.IsNullOrWhiteSpace(text))
        {
            return Task.FromResult<IEnumerable<Type>>(Type.EmptyTypes);
        }

        var components = _availableComponentTypes.Where(type => type.Name.Contains(text, StringComparison.OrdinalIgnoreCase));

        return Task.FromResult(components);
    }

    private async void OnSearchResult(Type? entry)
    {
        if (entry is null)
        {
            return;
        }

        _selectedType = entry;
        UpdateQueryString(entry);
        await Task.Yield();
        await _autocomplete.ClearAsync();
    }

    private static string? GetDescription(Type? type)
    {
        if (type is null)
        {
            return string.Empty;
        }

        var field = type.GetField("__description__", BindingFlags.Public | BindingFlags.Static | BindingFlags.GetField);
        if (field is null || field.FieldType != typeof(string))
        {
            return "This test component does not have a description. Field \"public static string __description__\" not found in this component.";
        }

        return (string?)field.GetValue(null);
    }
}
